package com.f.util;

import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.digest.HMac;
import cn.hutool.crypto.symmetric.SM4;
import cn.hutool.crypto.symmetric.SymmetricCrypto;

import java.nio.charset.StandardCharsets;
import java.util.Objects;

/**
 * @program: small
 * @description: sm系列加密工具
 * 基于hutool工具包实现sm3签名与验签，sm4的加密与解密，支持自定义秘钥与无秘钥方式
 * @author: tuyulong
 * @create: 2023-01-11 10:58
 **/
public class SMUtil {

    // 定义分组加密模式使用：SM4算法使用ECB模式，填充算法为PKCS5Padding
    public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS7Padding";

    /**
     * SM3加密 结果转大写
     * @param data  待加密数据
     * @param key   自定义秘钥，如果不传则不适用秘钥加密
     * @return
     */
    public static String getSm3SignStr(String data, String key){
        return getSm3SignStr(data, key, false);
    }

    /**
     * SM3加密 结果不转大写
     * @param data  待加密数据
     * @param key   自定义秘钥，如果不传则不适用秘钥加密
     * @param toLowerCase   true为转小写，false转大写
     * @return
     */
    public static String getSm3SignStr(String data, String key, boolean toLowerCase){
        byte[] bytes = sm3Sign(data, key);
        if (Objects.isNull(bytes)) {
            return null;
        }
        return HexUtil.encodeHexStr(bytes, toLowerCase);
    }

    /**
     * SM3加密
     * @param data  待加密数据
     * @param key   自定义秘钥，如果不传则不适用秘钥加密
     * @return
     */
    private static byte[] sm3Sign(String data, String key){
        if (StrUtil.isEmpty(data)) {
            return null;
        }
        if (StrUtil.isEmpty(key)) {
            return HexUtil.decodeHex(SmUtil.sm3(data));
        }
        HMac hMac = SmUtil.hmacSm3(key.getBytes(StandardCharsets.UTF_8));
        return hMac.digest(data);

    }

    /**
     * sm3验签
     * @param srcData
     * @param sm3HexStr
     * @param key
     * @param toLowerCase
     * @return
     */
    public static boolean sm3Verify(String srcData, String sm3HexStr, String key, boolean toLowerCase){
        String newStr = getSm3SignStr(srcData, key, toLowerCase);
        return StrUtil.equals(newStr, sm3HexStr);
    }

    /**
     * sm3验签 无秘钥验签
     * @param srcData
     * @param sm3HexStr
     * @param toLowerCase
     * @return
     */
    public static boolean sm3Verify(String srcData, String sm3HexStr, boolean toLowerCase){
        String newStr = getSm3SignStr(srcData, null, toLowerCase);
        return StrUtil.equals(newStr, sm3HexStr);
    }

    /**
     * sm4加密
     * @param data  待加密数据
     * @param key   加密秘钥，可为null
     * @param toLowerCase   true则返回小写，fasle则返回大写
     * @return
     */
    public static String getSm4EncryptStr(String data, String key, boolean toLowerCase) {
        byte[] bytes = sm4Encrypt(data, key, null);
        if (Objects.isNull(bytes)) {
            return null;
        }
        return HexUtil.encodeHexStr(bytes, toLowerCase);
    }

    /**
     * sm4加密
     * @param data  待加密数据
     * @param key   加密秘钥，可为null
     * @return
     */
    private static byte[] sm4Encrypt(String data, String key, String algorithm){
        if (StrUtil.isEmpty(data)) {
            return null;
        }
        if (StrUtil.isEmpty(algorithm)) {
            algorithm = ALGORITHM_NAME_ECB_PADDING;
        }
        if (StrUtil.isEmpty(key)) {
            SM4 sm4 = SmUtil.sm4();
            return sm4.encrypt(data.getBytes(StandardCharsets.UTF_8));
        }
        SymmetricCrypto symmetricCrypto = new SymmetricCrypto(algorithm, key.getBytes(StandardCharsets.UTF_8));
        return symmetricCrypto.encrypt(data.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * sm4解密
     * @param data  带解密数据
     * @param key   加密秘钥，可为null
     * @return
     */
    public static String getSm4DecryptStr(String data, String key) {
        byte[] bytes = sm4Decrypt(data, key, ALGORITHM_NAME_ECB_PADDING);
        if (Objects.isNull(bytes)) {
            return null;
        }
        return StrUtil.str(bytes, CharsetUtil.CHARSET_UTF_8);
    }

    /**
     * sm4解密
     * @param data  带解密数据
     * @param key   加密秘钥，可为null
     * @param algorithm   算法
     * @return
     */
    private static byte[] sm4Decrypt(String data, String key, String algorithm) {
        if (StrUtil.isEmpty(data)) {
            return null;
        }
        if (StrUtil.isEmpty(algorithm)) {
            algorithm = ALGORITHM_NAME_ECB_PADDING;
        }
        if (StrUtil.isEmpty(key)) {
            SM4 sm4 = SmUtil.sm4();
            return sm4.decrypt(data);
        }
        SymmetricCrypto symmetricCrypto = new SymmetricCrypto(algorithm, key.getBytes(StandardCharsets.UTF_8));
        return symmetricCrypto.decrypt(data);
    }
}
